package com.atguigu.gmall.item.service.impl;
import com.google.common.collect.Lists;

import java.math.BigDecimal;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;


import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.common.execption.GmallException;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.JSONs;
import com.atguigu.gmall.feign.product.SkuDetailFeignClient;
import com.atguigu.gmall.product.entity.SkuInfo;
import com.atguigu.gmall.product.entity.SpuSaleAttr;
import com.atguigu.gmall.item.to.CategoryView;
import com.atguigu.gmall.item.to.SkuDetailTO.SkuInfoVo;

import com.atguigu.gmall.item.service.SkuDetailService;
import com.atguigu.gmall.item.to.SkuDetailTO;
import com.atguigu.starter.cache.annotation.GmallCache;
import com.atguigu.starter.cache.service.BitmapService;
import com.atguigu.starter.cache.service.CacheService;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

/**
 * @author lfy
 * @Description
 * @create 2023-08-18 15:27
 */
@Slf4j
@Service
public class SkuDetailServiceImpl implements SkuDetailService {

    @Autowired
    SkuDetailFeignClient skuDetailFeignClient;

    //本地缓存；
    //1）、有可能oom？  1亿； java对象布局（32kb整数倍）
    //2）、
    Map<Long, SkuDetailTO> cache = new ConcurrentHashMap<>();

    @Autowired
    StringRedisTemplate redisTemplate;

    ReentrantLock rlock = new ReentrantLock(); //本地锁


    @Autowired
    CacheService cacheService;

    @Autowired
    BitmapService bitmapService;

    @Autowired
    RedissonClient redissonClient;



    @Autowired
    ThreadPoolExecutor executor;


    /**
     * 位图防随机值穿透攻击、分布式锁防击穿、缓存随机值防雪崩： 三防切面 + 动态表达式 + 自定义注解
     * @param skuId
     * @return
     */
    @GmallCache(
            key = RedisConst.SKU_INFO_CACHE_KEY+"#{#params[0]}",
            bitmapName = RedisConst.BITMAP_SKUIDS,
            bitmapOffsetExpr = "#{#params[0]}",
            dataTTl = RedisConst.SKU_INFO_TTL
    )
    @Override
    public SkuDetailTO getSkuDetail(Long skuId) {
        log.info("回源查询数据中...");
        return getSkuDetailTOFromSource(skuId);
    }

    /**
     * 高并发场景缓存使用逻辑
     * 要求：我们只写正常逻辑；
     *
     * 抽取： AOP场景；面向切面编程
     * 1、事务场景： @Transactional
     *      1）、获取链接、设置非自动提交
     *      2）、准备sql，设置参数【自己sql】
     *      3）、执行sql，封装结果【自己sql】
     *      4）、提交
     *      5）、关闭链接
     * 2、缓存场景： @GmallCache
     *      1）、查询缓存
     *      2）、查位图
     *      3）、加锁
     *      4）、回源；【各自回源查自己的数据】
     *      5）、保存数据
     *      6）、解锁
     *
     * @param skuId
     * @return
     */

    public SkuDetailTO getSkuDetailWithRedisson(Long skuId) {
        String skuCacheKey = RedisConst.SKU_INFO_CACHE_KEY + skuId;
        //1、缓存中有没有
        SkuDetailTO cacheData = cacheService.get(skuCacheKey, SkuDetailTO.class);

        //2、缓存中有直接返回
        if (cacheData != null) {
            return cacheData;
        }

        //3、缓存没有
        log.info("缓存未命中，准备回源: 步骤1: 先看位图...");
        boolean contains = bitmapService.contains(RedisConst.BITMAP_SKUIDS, skuId); //【防随机值穿透攻击】
        if (!contains) {
            //3.1 位图没有就是攻击请求
            log.warn("疑似攻击请求，已被拦截，访问商品：[{}]", skuId);
            throw new GmallException(ResultCodeEnum.SKU_NOT_EXIST);
        }


        //4、位图有，可以进行回源； 加锁：【防击穿】  lock:sku:info:49
        RLock lock = redissonClient.getLock(RedisConst.LOCK_PREFIX + skuCacheKey);
        boolean b = lock.tryLock();
        if (b) {
            try {
                //5、加锁成功，可以回源
                log.info("缓存未命中，开始回源: 步骤2: 加锁放击穿完成...");
                SkuDetailTO source = getSkuDetailTOFromSource(skuId);
                //6、把回源的数据放入缓存； 商品详情保存15天； 所有的时间都以毫秒为单位
                cacheService.save(skuCacheKey, source, RedisConst.SKU_INFO_TTL);
                return source;
            } finally {
                try {
                    lock.unlock();
                } catch (Exception e) {
                    log.warn("回源完成，差点解了别人的锁...");
                }
            }
        }


        //7、击穿期没有抢到锁
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        log.info("缓存未命中，回源时抢锁失败，等待2s查询缓存，结束");
        //8、只需要查缓存即可
        return cacheService.get(skuCacheKey, SkuDetailTO.class);
    }

    /**
     * 简单逻辑缓存使用：高并发场景不可以这么写
     *
     * @param skuId
     * @return
     */
    public SkuDetailTO getSkuDetailSimple(Long skuId) {


        //1、先看缓存
        String json = redisTemplate.opsForValue().get(RedisConst.SKU_INFO_CACHE_KEY + skuId);

        //2、缓存中有
        if (!StringUtils.isEmpty(json)) {
            //逆转并返回；  序列化与反序列化
            return JSONs.jsonStrToObj(json, SkuDetailTO.class);
        }

        rlock.lock(); //更细粒度，49号商品用49号专用锁。

        //3、缓存中没有；回源
        SkuDetailTO source = getSkuDetailTOFromSource(skuId);
        //4、放入缓存， ""
        redisTemplate.opsForValue().set(RedisConst.SKU_INFO_CACHE_KEY + skuId, JSONs.toJSONStr(source));
        rlock.unlock();
        return source;
    }


    /**
     * 使用本地缓存
     *
     * @param skuId
     * @return
     */
    public SkuDetailTO getSkuDetailLocalCache(Long skuId) {

        //1、先看缓存
        SkuDetailTO cacheData = cache.get(skuId);
        if (cacheData != null) {
            //2、缓存中有直接返回
            return cacheData;
        }

        //3、缓存中没有，回源查询
        SkuDetailTO detail = getSkuDetailTOFromSource(skuId);
        //4、把回源的数据，放入缓存
        cache.put(skuId, detail);

        return detail;
    }


    /**
     * 从数据源头要到商品详情数据
     *
     * 1、异步优化：加速访问
     *   1）、线程池
     *   2）、异步编排:  CompletableFuture   异步编排 = 编组 + 排列；
     *      K8s：Kubernetes： 容器编排（自动扩缩容、故障转移、自动修复、负载均衡、灰度发布）
     *      Elatic-Job：任务【编排】；  xxl-job
     *
     *
     *      大量异步场景下； 异步任务之前又有着千丝万缕的关系，写起来很麻烦
     *      ABCDEFGH：这些事情有写可以并发，有些需要同步等待
     *
     *      商品详情查询：
     *          商品分类层级查询：
     *          商品销售属性：
     *          商品valueSkuJson查询：
     *      商品图片：
     *      商品价格：
     *
     *
     * @param skuId
     * @return
     */
    private SkuDetailTO getSkuDetailTOFromSource(Long skuId) {
        log.info("商品详情：【{}】； 缓存未命中；正在回源", skuId);
        SkuDetailTO detail = new SkuDetailTO();

        //封装sku基本信息
        SkuInfoVo infoVo = new SkuInfoVo();
        //1、基本信息
        CompletableFuture<SkuInfo> skuInfoFuture = CompletableFuture.supplyAsync(() -> {
            SkuInfo skuInfo = skuDetailFeignClient.getSkuInfo(skuId).getData();

            infoVo.setId(skuInfo.getId());
            infoVo.setSkuName(skuInfo.getSkuName());
            infoVo.setSpuId(skuInfo.getSpuId());
            infoVo.setSkuDefaultImg(skuInfo.getSkuDefaultImg());
            infoVo.setWeight(skuInfo.getWeight());

            detail.setSkuInfo(infoVo);
            return skuInfo;
        }, executor);




        //2、商品图片
        CompletableFuture<Void> imageFuture = CompletableFuture.runAsync(() -> {
            List<SkuDetailTO.SkuImageVo> imageVos = skuDetailFeignClient.getImages(skuId)
                    .getData().stream()
                    .map(item -> {
                        SkuDetailTO.SkuImageVo imageVo = new SkuDetailTO.SkuImageVo();
                        imageVo.setImgUrl(item.getImgUrl());
                        return imageVo;
                    }).collect(Collectors.toList());
            //填充图片
            infoVo.setSkuImageList(imageVos);
            detail.setSkuInfo(infoVo);
        }, executor);


        //3、分类路径
        CompletableFuture<Void> categoryFuture = skuInfoFuture.thenAcceptAsync(result -> {
            CategoryView categoryView = skuDetailFeignClient.getCategoryView(result.getCategory3Id()).getData();
            detail.setCategoryView(categoryView);
        },executor);


        //4、商品价格，现查
        CompletableFuture<Void> priceFuture = CompletableFuture.runAsync(() -> {
            BigDecimal decimal = skuDetailFeignClient.getPrice(skuId).getData();
            detail.setPrice(decimal);
        },executor);


        //5、销售属性
        CompletableFuture<Void> spuSaleAttrFuture = skuInfoFuture.thenAcceptAsync(result -> {
            List<SpuSaleAttr> data = skuDetailFeignClient.getSpuSaleAttr(result.getSpuId(), skuId).getData();
            detail.setSpuSaleAttrList(data);
        },executor);


        //6、valuesSkuJson: 告诉前端每一种组合对应的是哪个商品。
        CompletableFuture<Void> valuesSkuJsonFuture = skuInfoFuture.thenAcceptAsync(result -> {
            String json = skuDetailFeignClient.getValueSkuJson(result.getSpuId()).getData();
            detail.setValuesSkuJson(json);
        },executor);


        CompletableFuture
                .allOf(imageFuture,categoryFuture,priceFuture,spuSaleAttrFuture,valuesSkuJsonFuture)
                .join();

        return detail;
    }
}
